Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / feature / car / src / test / kotlin / org / meshtastic / feature / car / util / CarScreenDataBuilderTest.kt

Displaying Raw • Download

feature/car/src/test/kotlin/org/meshtastic/feature/car/util/CarScreenDataBuilderTest.kt 2d20cd8a4708e2ef66e98d5259f3a97ec93f240a (2d20cd8a) Text, 20.62 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(Ta5d6ff"Ta5d6ffINVISIBLE_REFERENCETa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffINVISIBLE_MEMBERTa5d6ff"Tb4b4b4)

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

Tff7b72package T7ee787org.meshtastic.feature.car.util

Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.util.onlineTimeThreshold
Tff7b72import T7ee787org.meshtastic.feature.car.model.ConversationUi
Tff7b72import T7ee787org.meshtastic.feature.car.model.SignalQuality
Tff7b72import T7ee787org.meshtastic.feature.car.service.MessageSnapshot
Tff7b72import T7ee787org.meshtastic.proto.Config.LoRaConfig.ModemPreset
Tff7b72import T7ee787org.meshtastic.proto.DeviceMetrics
Tff7b72import T7ee787org.meshtastic.proto.LocalStats
Tff7b72import T7ee787org.meshtastic.proto.Position
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue

Tff7b72class T56d364CarScreenDataBuilderTest Tb4b4b4{

T8b949e// determineSignalQuality() — preset-relative SNR, RSSI not used (issue #5446)

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns unknown when snr is absent`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Absence is UNKNOWN, not NONE: NONE claims a measured link too weak to demodulate.
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Tff7b72nullTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3UNKNOWNTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality rates a zero snr reading`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(T79c0ff0fTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3EXCELLENTTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi resolves an unset node snr to unknown`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Exercises the production path: fails if buildNodeUi reverts to reading node.snr, which would feed the
T8b949e// Float.MAX_VALUE sentinel into the bands and rate a node with no reading as EXCELLENT.
Tff7b72val Te6edf3node Tff7b72= Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3UNKNOWNTb4b4b4, Te6edf3uiTb4b4b4.Te6edf3signalQualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi rates a zero node snr reading`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1Tb4b4b4, Te6edf3snr Tff7b72= T79c0ff0fTb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3EXCELLENTTb4b4b4, Te6edf3uiTb4b4b4.Te6edf3signalQualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns excellent well above the preset floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// LongFast floor -17.5; -10 is 7.5 dB above it (> floor + 5.5 margin).
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff1T79c0ff0fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3EXCELLENTTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns good just above the preset floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff1T79c0ff5fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3GOODTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns fair just below the preset floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff2T79c0ff0fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3FAIRTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns bad further below the floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff2T79c0ff4fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3BADTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality returns none far below the floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff3T79c0ff0fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_FASTTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3NONETb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality rates the same snr relative to the preset`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// -15 dB clears LongSlow's -20 floor but sits at/below ShortFast's -7.5 floor.
Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3GOODTb4b4b4, Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Tff7b72-T79c0ff1T79c0ff5fTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_SLOWTb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3BADTb4b4b4, Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Tff7b72-T79c0ff1T79c0ff5fTb4b4b4, Te6edf3ModemPresetTb4b4b4.Te6edf3SHORT_FASTTb4b4b4)Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality uses the corrected LongSlow SF12 floor`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// -19 dB clears the physically-correct -20 floor (GOOD); it would be NONE under Apple's bugged -7.5.
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff1T79c0ff9fTb4b4b4, Te6edf3modemPreset Tff7b72= Te6edf3ModemPresetTb4b4b4.Te6edf3LONG_SLOWTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3GOODTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`determineSignalQuality falls back to LongFast for a null preset`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3quality Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3determineSignalQualityTb4b4b4(Te6edf3snr Tff7b72= Tff7b72-T79c0ff1T79c0ff0fTb4b4b4, Te6edf3modemPreset Tff7b72= Tff7b72nullTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3EXCELLENTTb4b4b4, Te6edf3qualityTb4b4b4)
Tb4b4b4}

T8b949e// buildNodeUi()

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi maps online node with all display fields`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72=
Te6edf3createNodeTb4b4b4(
Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff1Tb4b4b4,
Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ffAlpha BaseTa5d6ff"Tb4b4b4,
Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ffABTa5d6ff"Tb4b4b4,
Te6edf3snr Tff7b72= Tff7b72-T79c0ff4fTb4b4b4,
Te6edf3rssi Tff7b72= Tff7b72-T79c0ff1T79c0ff0T79c0ff8Tb4b4b4,
Te6edf3lastHeard Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff1T79c0ff2T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff8T79c0ff7Tb4b4b4)Tb4b4b4,
Te6edf3position Tff7b72= Te6edf3validPositionTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0T79c0ff1Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3nodeNumTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffAlpha BaseTa5d6ff"Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3longNameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffABTa5d6ff"Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3shortNameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3SignalQualityTb4b4b4.Te6edf3EXCELLENTTb4b4b4, Te6edf3uiTb4b4b4.Te6edf3signalQualityTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff8T79c0ff7Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3batteryPercentTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3isOnlineTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4, Te6edf3uiTb4b4b4.Te6edf3lastHeardTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3hasPositionTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi marks offline node from stale last heard`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff2Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3offlineLastHeardTb4b4b4(T79c0ff6T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertFalseTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3isOnlineTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3lastHeardTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4, Te6edf3uiTb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi falls back when names are empty`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff3Tb4b4b4, Te6edf3longName Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3shortName Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffUnknownTa5d6ff"Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3longNameTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff?Ta5d6ff"Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3shortNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi keeps valid battery percentage`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff4Tb4b4b4, Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff4T79c0ff2Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4, Te6edf3uiTb4b4b4.Te6edf3batteryPercentTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi drops zero battery percentage`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff5Tb4b4b4, Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertNullTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3batteryPercentTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi drops battery values above one hundred`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff6Tb4b4b4, Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff1T79c0ff0T79c0ff1Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertNullTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3batteryPercentTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi returns null battery when metrics do not include one`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff7Tb4b4b4, Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertNullTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3batteryPercentTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi marks node without position as lacking location`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff8Tb4b4b4, Te6edf3position Tff7b72= Te6edf3PositionTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertFalseTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3hasPositionTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildNodeUi ignores invalid position coordinates`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3node Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff0T79c0ff9Tb4b4b4, Te6edf3position Tff7b72= Te6edf3PositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff9T79c0ff1T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= Tff7b72-T79c0ff1T79c0ff2T79c0ff2T79c0ff4T79c0ff1T79c0ff9T79c0ff4T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3ui Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildNodeUiTb4b4b4(Te6edf3nodeTb4b4b4)

Te6edf3assertFalseTb4b4b4(Te6edf3uiTb4b4b4.Te6edf3hasPositionTb4b4b4)
Tb4b4b4}

T8b949e// sortNodes()

Tf0883e@Test
Tff7b72fun Td2a8ff`sortNodes places online nodes before offline nodes`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3onlineRecent Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff1Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff2T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3offlineRecent Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff2Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3offlineLastHeardTb4b4b4(T79c0ff5Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3onlineOlder Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff3Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff1T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3offlineOlder Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff4Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3sorted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3sortNodesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3offlineRecentTb4b4b4, Te6edf3onlineOlderTb4b4b4, Te6edf3offlineOlderTb4b4b4, Te6edf3onlineRecentTb4b4b4)Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff2T79c0ff0T79c0ff1Tb4b4b4, T79c0ff2T79c0ff0T79c0ff3Tb4b4b4, T79c0ff2T79c0ff0T79c0ff2Tb4b4b4, T79c0ff2T79c0ff0T79c0ff4Tb4b4b4)Tb4b4b4, Te6edf3sortedTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3nodeNum Tb4b4b4}Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3sortedTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3isOnlineTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3sortedTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3isOnlineTb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3sortedTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3isOnlineTb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3sortedTff7b72[T79c0ff3Tff7b72]Tb4b4b4.Te6edf3isOnlineTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`sortNodes orders nodes by last heard descending within online and offline groups`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3onlineNewest Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff5Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff4T79c0ff0T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3onlineOldest Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff6Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff5T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3offlineNewest Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff7Tb4b4b4, Te6edf3lastHeard Tff7b72= Te6edf3offlineLastHeardTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3offlineOldest Tff7b72= Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff2T79c0ff0T79c0ff8Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)

Tff7b72val Te6edf3sorted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3sortNodesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3offlineOldestTb4b4b4, Te6edf3offlineNewestTb4b4b4, Te6edf3onlineOldestTb4b4b4, Te6edf3onlineNewestTb4b4b4)Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff2T79c0ff0T79c0ff5Tb4b4b4, T79c0ff2T79c0ff0T79c0ff6Tb4b4b4, T79c0ff2T79c0ff0T79c0ff7Tb4b4b4, T79c0ff2T79c0ff0T79c0ff8Tb4b4b4)Tb4b4b4, Te6edf3sortedTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3nodeNum Tb4b4b4}Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3sortedTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3lastHeard Tff7b72> Te6edf3sortedTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3lastHeardTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3sortedTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3lastHeard Tff7b72> Te6edf3sortedTff7b72[T79c0ff3Tff7b72]Tb4b4b4.Te6edf3lastHeardTb4b4b4)
Tb4b4b4}

T8b949e// sortConversations()

Tf0883e@Test
Tff7b72fun Td2a8ff`sortConversations orders conversations by newest message first`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldest Tff7b72= Te6edf3createConversationTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6ff0!oldTa5d6ff"Tb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffOldTa5d6ff"Tb4b4b4, Te6edf3lastMessageTime Tff7b72= T79c0ff1Te6edf3_000LTb4b4b4)
Tff7b72val Te6edf3newest Tff7b72= Te6edf3createConversationTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6ff0!newTa5d6ff"Tb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffNewTa5d6ff"Tb4b4b4, Te6edf3lastMessageTime Tff7b72= T79c0ff5Te6edf3_000LTb4b4b4)
Tff7b72val Te6edf3middle Tff7b72= Te6edf3createConversationTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6ff0!midTa5d6ff"Tb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffMidTa5d6ff"Tb4b4b4, Te6edf3lastMessageTime Tff7b72= T79c0ff3Te6edf3_000LTb4b4b4)

Tff7b72val Te6edf3sorted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3sortConversationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3oldestTb4b4b4, Te6edf3newestTb4b4b4, Te6edf3middleTb4b4b4)Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(Ta5d6ff"Ta5d6ff0!newTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff0!midTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff0!oldTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3sortedTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3contactKey Tb4b4b4}Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`sortConversations keeps single conversation unchanged`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3conversation Tff7b72= Te6edf3createConversationTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6ff0!soloTa5d6ff"Tb4b4b4, Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffSoloTa5d6ff"Tb4b4b4, Te6edf3lastMessageTime Tff7b72= T79c0ff7Te6edf3_000LTb4b4b4)

Tff7b72val Te6edf3sorted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3sortConversationsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3conversationTb4b4b4)Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3conversationTb4b4b4)Tb4b4b4, Te6edf3sortedTb4b4b4)
Tb4b4b4}

T8b949e// buildLocalStats()

Tf0883e@Test
Tff7b72fun Td2a8ff`buildLocalStats uses populated local stats when available`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3ourNode Tff7b72=
Te6edf3createNodeTb4b4b4(
Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff1Tb4b4b4,
Te6edf3deviceMetrics Tff7b72=
Te6edf3DeviceMetricsTb4b4b4(
Te6edf3battery_level Tff7b72= T79c0ff8T79c0ff2Tb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff1T79c0ff0.5fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff2.5fTb4b4b4,
Te6edf3uptime_seconds Tff7b72= T79c0ff1T79c0ff2T79c0ff0Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3stats Tff7b72=
Te6edf3LocalStatsTb4b4b4(
Te6edf3uptime_seconds Tff7b72= T79c0ff7Te6edf3_200Tb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff6T79c0ff5.5fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff1T79c0ff2.25fTb4b4b4,
Te6edf3num_packets_tx Tff7b72= T79c0ff9T79c0ff1Tb4b4b4,
Te6edf3num_packets_rx Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3allNodes Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3ourNodeTb4b4b4, Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff3Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3localStats Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildLocalStatsTb4b4b4(Te6edf3ourNode Tff7b72= Te6edf3ourNodeTb4b4b4, Te6edf3stats Tff7b72= Te6edf3statsTb4b4b4, Te6edf3allNodes Tff7b72= Te6edf3allNodesTb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff8T79c0ff2Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3batteryLevelTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3localStatsTb4b4b4.Te6edf3hasBatteryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff6T79c0ff5.5fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3channelUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff2.25fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3airUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3totalNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3onlineNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff7Te6edf3_200Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3uptimeSecondsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff9T79c0ff1Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsTxTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsRxTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildLocalStats falls back to device metrics when local stats have no uptime`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3ourNode Tff7b72=
Te6edf3createNodeTb4b4b4(
Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff4Tb4b4b4,
Te6edf3deviceMetrics Tff7b72=
Te6edf3DeviceMetricsTb4b4b4(
Te6edf3battery_level Tff7b72= T79c0ff5T79c0ff4Tb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff2T79c0ff2.5fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff8.75fTb4b4b4,
Te6edf3uptime_seconds Tff7b72= T79c0ff3Te6edf3_600Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3stats Tff7b72=
Te6edf3LocalStatsTb4b4b4(
Te6edf3uptime_seconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff9T79c0ff9.9fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff9T79c0ff9.9fTb4b4b4,
Te6edf3num_packets_tx Tff7b72= T79c0ff1T79c0ff1Tb4b4b4,
Te6edf3num_packets_rx Tff7b72= T79c0ff1T79c0ff7Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3allNodes Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3ourNodeTb4b4b4, Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff5Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3localStats Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildLocalStatsTb4b4b4(Te6edf3ourNode Tff7b72= Te6edf3ourNodeTb4b4b4, Te6edf3stats Tff7b72= Te6edf3statsTb4b4b4, Te6edf3allNodes Tff7b72= Te6edf3allNodesTb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff5T79c0ff4Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3batteryLevelTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3localStatsTb4b4b4.Te6edf3hasBatteryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2T79c0ff2.5fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3channelUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff8.75fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3airUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3totalNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3onlineNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Te6edf3_600Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3uptimeSecondsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff1Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsTxTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff7Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsRxTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildLocalStats handles null local node by using zeros and node counts`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3stats Tff7b72=
Te6edf3LocalStatsTb4b4b4(
Te6edf3uptime_seconds Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3channel_utilization Tff7b72= T79c0ff1T79c0ff4.5fTb4b4b4,
Te6edf3air_util_tx Tff7b72= T79c0ff6.5fTb4b4b4,
Te6edf3num_packets_tx Tff7b72= T79c0ff3T79c0ff3Tb4b4b4,
Te6edf3num_packets_rx Tff7b72= T79c0ff4T79c0ff4Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3allNodes Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff6Tb4b4b4)Tb4b4b4, Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff7Tb4b4b4, Te6edf3lastHeard Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4, Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff8Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3localStats Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildLocalStatsTb4b4b4(Te6edf3ourNode Tff7b72= Tff7b72nullTb4b4b4, Te6edf3stats Tff7b72= Te6edf3statsTb4b4b4, Te6edf3allNodes Tff7b72= Te6edf3allNodesTb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3batteryLevelTb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3localStatsTb4b4b4.Te6edf3hasBatteryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3channelUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3airUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3totalNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3onlineNodesTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3uptimeSecondsTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff3T79c0ff3Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsTxTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff4Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3numPacketsRxTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildLocalStats reports no battery when local node metrics omit it`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3ourNode Tff7b72=
Te6edf3createNodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff3T79c0ff0T79c0ff9Tb4b4b4, Te6edf3deviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Te6edf3channel_utilization Tff7b72= T79c0ff5.5fTb4b4b4, Te6edf3air_util_tx Tff7b72= T79c0ff1.5fTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3stats Tff7b72= Te6edf3LocalStatsTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3localStats Tff7b72=
Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildLocalStatsTb4b4b4(Te6edf3ourNode Tff7b72= Te6edf3ourNodeTb4b4b4, Te6edf3stats Tff7b72= Te6edf3statsTb4b4b4, Te6edf3allNodes Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3ourNodeTb4b4b4)Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3batteryLevelTb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3localStatsTb4b4b4.Te6edf3hasBatteryTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5.5fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3channelUtilizationTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1.5fTb4b4b4, Te6edf3localStatsTb4b4b4.Te6edf3airUtilizationTb4b4b4)
Tb4b4b4}

T8b949e// formatUptime()

Tf0883e@Test
Tff7b72fun Td2a8ff`formatUptime returns zero minutes for seconds below a minute`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3formatted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3formatUptimeTb4b4b4(T79c0ff5T79c0ff9Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff0mTa5d6ff"Tb4b4b4, Te6edf3formattedTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`formatUptime returns whole minutes when under one hour`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3formatted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3formatUptimeTb4b4b4(T79c0ff1T79c0ff2T79c0ff0Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff2mTa5d6ff"Tb4b4b4, Te6edf3formattedTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`formatUptime returns hours and minutes when under one day`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3formatted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3formatUptimeTb4b4b4(T79c0ff3Te6edf3_900Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff1h 5mTa5d6ff"Tb4b4b4, Te6edf3formattedTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`formatUptime returns days and hours when at least one day`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3formatted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3formatUptimeTb4b4b4(T79c0ff9T79c0ff7Te6edf3_200Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff1d 3hTa5d6ff"Tb4b4b4, Te6edf3formattedTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`formatUptime drops leftover minutes once day format is used`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3formatted Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3formatUptimeTb4b4b4(T79c0ff1T79c0ff7T79c0ff6Te6edf3_460Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff2d 1hTa5d6ff"Tb4b4b4, Te6edf3formattedTb4b4b4)
Tb4b4b4}

T8b949e// recentMessages()

Tf0883e@Test
Tff7b72fun Td2a8ff`recentMessages returns default max number of latest messages`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3messages Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.T79c0ff7Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72> Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= Te6edf3indexTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3index Tff7b72* T79c0ff1Te6edf3_000LTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3recent Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3recentMessagesTb4b4b4(Te6edf3messagesTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff3Tb4b4b4, T79c0ff4Tb4b4b4, T79c0ff5Tb4b4b4, T79c0ff6Tb4b4b4, T79c0ff7Tb4b4b4)Tb4b4b4, Te6edf3recentTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tb4b4b4}Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3recentTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`recentMessages respects explicit limit`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3messages Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.T79c0ff5Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72> Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= Te6edf3indexTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3index Tff7b72* T79c0ff1Te6edf3_000LTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3recent Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3recentMessagesTb4b4b4(Te6edf3messagesTb4b4b4, Te6edf3limit Tff7b72= T79c0ff2Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff4Tb4b4b4, T79c0ff5Tb4b4b4)Tb4b4b4, Te6edf3recentTb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tb4b4b4}Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`recentMessages returns all messages when fewer than limit`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3messages Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= T79c0ff1Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff1Te6edf3_000LTb4b4b4)Tb4b4b4, Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= T79c0ff2Tb4b4b4, Te6edf3timestamp Tff7b72= T79c0ff2Te6edf3_000LTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3recent Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3recentMessagesTb4b4b4(Te6edf3messagesTb4b4b4, Te6edf3limit Tff7b72= T79c0ff5Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3messagesTb4b4b4, Te6edf3recentTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`recentMessages returns empty list when limit is zero`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3messages Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.T79c0ff3Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72> Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= Te6edf3indexTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3index Tff7b72* T79c0ff1Te6edf3_000LTb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3recent Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3recentMessagesTb4b4b4(Te6edf3messagesTb4b4b4, Te6edf3limit Tff7b72= T79c0ff0Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3recentTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}

T8b949e// buildContactKey() and constants

Tf0883e@Test
Tff7b72fun Td2a8ff`buildContactKey appends broadcast suffix`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3contactKey Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildContactKeyTb4b4b4(Te6edf3channelIndex Tff7b72= T79c0ff3Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff3^allTa5d6ff"Tb4b4b4, Te6edf3contactKeyTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`buildContactKey supports zero channel`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3contactKey Tff7b72= Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3buildContactKeyTb4b4b4(Te6edf3channelIndex Tff7b72= T79c0ff0Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ff0^allTa5d6ff"Tb4b4b4, Te6edf3contactKeyTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`max conversation messages constant matches car conversation limit`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3messages Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.T79c0ff8Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72> Te6edf3createMessageTb4b4b4(Te6edf3id Tff7b72= Te6edf3indexTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3index Tff7b72* T79c0ff1Te6edf3_000LTb4b4b4) Tb4b4b4}

Te6edf3assertEqualsTb4b4b4(T79c0ff5Tb4b4b4, Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3MAX_CONVERSATION_MESSAGESTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`max conversations constant matches messaging list limit`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4, Te6edf3CarScreenDataBuilderTb4b4b4.Te6edf3MAX_CONVERSATIONSTb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffcreateNodeTb4b4b4(
Te6edf3numTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3longNameTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ffNode Tffd700$Te6edf3numTa5d6ff"Tb4b4b4,
Te6edf3shortNameTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ffNTffd700$Te6edf3numTa5d6ff"Tb4b4b4,
Te6edf3snrTb4b4b4: Tffa657Float Tff7b72= Tff7b72-T79c0ff6fTb4b4b4,
Te6edf3rssiTb4b4b4: Tffa657Int Tff7b72= Tff7b72-T79c0ff1T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3lastHeardTb4b4b4: Tffa657Int Tff7b72= Te6edf3onlineLastHeardTb4b4b4(T79c0ff6T79c0ff0Tb4b4b4)Tb4b4b4,
Te6edf3deviceMetricsTb4b4b4: Te6edf3DeviceMetrics Tff7b72= Te6edf3DeviceMetricsTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3positionTb4b4b4: Te6edf3Position Tff7b72= Te6edf3validPositionTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3Node Tb4b4b4{
Tff7b72val Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!Tffd700$Te6edf3numTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Te6edf3longNameTb4b4b4, Te6edf3short_name Tff7b72= Te6edf3shortNameTb4b4b4)

Tff7b72return Te6edf3NodeTb4b4b4(
Te6edf3num Tff7b72= Te6edf3numTb4b4b4,
Te6edf3user Tff7b72= Te6edf3userTb4b4b4,
Te6edf3snr Tff7b72= Te6edf3snrTb4b4b4,
Te6edf3rssi Tff7b72= Te6edf3rssiTb4b4b4,
Te6edf3lastHeard Tff7b72= Te6edf3lastHeardTb4b4b4,
Te6edf3deviceMetrics Tff7b72= Te6edf3deviceMetricsTb4b4b4,
Te6edf3position Tff7b72= Te6edf3positionTb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffcreateConversationTb4b4b4(Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4, Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4, Te6edf3lastMessageTimeTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Te6edf3ConversationUi Tff7b72=
Te6edf3ConversationUiTb4b4b4(
Te6edf3contactKey Tff7b72= Te6edf3contactKeyTb4b4b4,
Te6edf3displayName Tff7b72= Te6edf3nameTb4b4b4,
Te6edf3lastMessage Tff7b72= Ta5d6ff"Ta5d6ffLatest from Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4,
Te6edf3lastMessageTime Tff7b72= Te6edf3lastMessageTimeTb4b4b4,
Te6edf3unreadCount Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3isEmergency Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffcreateMessageTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3timestampTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Te6edf3MessageSnapshot Tff7b72= Te6edf3MessageSnapshotTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3senderName Tff7b72= Ta5d6ff"Ta5d6ffSender Tffd700$Te6edf3idTa5d6ff"Tb4b4b4,
Te6edf3text Tff7b72= Ta5d6ff"Ta5d6ffMessage Tffd700$Te6edf3idTa5d6ff"Tb4b4b4,
Te6edf3timestamp Tff7b72= Te6edf3timestampTb4b4b4,
Te6edf3isFromMe Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffvalidPositionTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3Position Tff7b72= Te6edf3PositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff3T79c0ff7T79c0ff7T79c0ff7T79c0ff4T79c0ff9T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= Tff7b72-T79c0ff1T79c0ff2T79c0ff2T79c0ff4T79c0ff1T79c0ff9T79c0ff4T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffonlineLastHeardTb4b4b4(Te6edf3offsetSecondsTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Te6edf3onlineTimeThresholdTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3offsetSeconds

Tff7b72private Tff7b72fun Td2a8ffofflineLastHeardTb4b4b4(Te6edf3offsetSecondsTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Te6edf3onlineTimeThresholdTb4b4b4(Tb4b4b4) Tff7b72- Te6edf3offsetSeconds
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.15s